<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  
  <title>
    BLE GATT | 朝雨绫萧的博客
  </title>
  <meta content="width=device-width, initial-scale=1" name="viewport">
  <meta name="theme-color" content="#4184f3">
  
  
  <link href="/image/favicon.png" rel="icon" />
  

  
<link rel="stylesheet" href="/css/highlight.light.css">

  
<link rel="stylesheet" href="/css/prism-customize.css">

  
<link rel="stylesheet" href="/css/nav-icon.css">

  
<link rel="stylesheet" href="/css/waves.min.css">

  
<link rel="stylesheet" href="/css/jquery.tocify.css">

  
<link rel="stylesheet" href="/css/main.css">

  
<link rel="stylesheet" href="/css/nav-indicator.css">

  
<link rel="stylesheet" href="/css/twikoo.css">

  
<link rel="stylesheet" href="/css/search.css">

  
  

  

  </meta>
  </meta>
  <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.js"></script>
  
<script src="/js/comment.js"></script>

  
<script src="/js/copy.js"></script>

<meta name="generator" content="Hexo 5.4.0"></head>

	<body>
		<header>
			<!-- cover image or sth. -->
		</header>
		<div id="main" class="m-scene">
			
<div class="nav-wrapper" id="header">

    <div class="container">
        <nav>
            
            
            <div class="logo wave">
                <a href="/" id="logo">
                    朝雨绫萧的博客
                </a>
            </div>

            

            <div class="nav-toggle-icon" >
                <div class="material-hamburger">
                    <span>
                    </span>
                    <span>
                    </span>
                    <span>
                    </span>
                </div>
            </div>

            <div class="menu-wrapper">
                <div class="nav-indicator">
                </div>
                <ul class="menus">
                    
                     
                        <li>
                            <a class="wave " href="/">
                                首页
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/archives">
                                归档
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/about">
                                关于
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/message">
                                留言
                            </a>
                        </li>
                     
                        <li>
                            <a class="wave " href="/search">
                                搜索
                            </a>
                        </li>
                     
                    
                   
                </ul>
            </div>

        </nav>
    </div>
</div>


				<div class="container content">
					<div class="scene_element scene_element--fadein">
						<div class="row">
  <div class="main main-article">
    <article>

      
        <header class="post-header no-cover">
      
        </header>

      <!-- 标题 -->
      <h1 class="post-title">
        BLE GATT
      </h1>

      <section class="post-info post-info-bottom">

        <!-- 发布时间 -->
        <span class="post-date">
          <span class="post-date-text">
            发布于: 2021-02-28
          </span>
        </span>
        
        <!-- 分类 -->
        
          <span class="post-category">分类:
            <a class="article-category-link" href="/categories/BLE/">BLE</a>
          </span>
        

        <!-- 标签 -->
        
          <span class="post-tags">
            <ul class="post-tag-list" itemprop="keywords"><li class="post-tag-list-item"><a class="post-tag-list-link" href="/tags/BLE/" rel="tag">BLE</a></li></ul>
          </span>
        

        <!-- 阅读量统计 -->
        

      </section>

      <!-- 正文 -->
      <section class="post-content">
        <h1 id="通用属性规范GATT"><a href="#通用属性规范GATT" class="headerlink" title="通用属性规范GATT"></a>通用属性规范GATT</h1><p>GATT(通用属性配置)使用ATT协议定义了服务框架。该框架定义服务的程序和格式以及其特征(characteristics)，定义的程序包括发现，读取，写入，通知和指示特性，以及配置特性的广播。<br>GATT配置文件旨在供应用程序或其他配置文件使用，以便客户端可以与服务器进行通信。服务器包含许多属性，GATT配置文件定义了如何使用属性协议来发现，读取，写入和获取这些属性的指示，以及配置属性的广播。 </p>
<h2 id="服务器属性数据库"><a href="#服务器属性数据库" class="headerlink" title="服务器属性数据库"></a>服务器属性数据库</h2><p>一个服务器至少有一个 profile，一个profile可以由至少一个或多个Server 组成。而 一个Server 的由0 或多个引用服务（Include）和至少一个特性（Characteristic） 组成。一个特性包括特性声明，特性值声明和特性描述符。</p>
<p>每个设备都包含以下必要的特征值和服务项：</p>
<p>PROFILE</p>
<ul>
<li>Generic Access Service（Primary Service）<ul>
<li>Device Name（Characteristic）</li>
<li>Appearance（Characteristic）</li>
<li>Peripheral Preferred Connection Parameters（Characteristic）</li>
</ul>
</li>
<li>Generic Attribute Service（Primary Service）<ul>
<li>Service Changed（Characteristic）</li>
<li>CCCD（Descriptor）</li>
</ul>
</li>
</ul>
<p>客户端和服务器绑定后，客户端可以缓存服务器的服务集和特性集，当服务器的增加或者删除服务后，会通过 GATT 变更服务通知客户端。</p>
<p>一个组开始于一个声明， 服务分组结束于下一个服务声明。特性分组结束于下一个特性的声明或者是下一个服务的声明 。</p>
<p>以下为属性数据库的示例：</p>
<table>
<thead>
<tr>
<th>Handle</th>
<th>UUID（type）</th>
<th>value（Attribute Value）</th>
</tr>
</thead>
<tbody><tr>
<td>0x0001</td>
<td>0x2800(Primary Service)</td>
<td>0x1800(GAP Service)</td>
</tr>
<tr>
<td>0x0002</td>
<td>0x2803(Characteristic)</td>
<td>0x0A(Properties), 0X0003（handle）, 0x2A00（uuid）</td>
</tr>
<tr>
<td>0x0003</td>
<td>0x2A00(Device Name)</td>
<td>“example device”</td>
</tr>
<tr>
<td>0x0010</td>
<td>0x2800(Primary Service)</td>
<td>0x1801(GATT Service)</td>
</tr>
<tr>
<td>0x0100</td>
<td>0x2800(Primary Service)</td>
<td>0x180A(Battery State Service)</td>
</tr>
<tr>
<td>0x0101</td>
<td>0x2803(Characteristic)</td>
<td>0x02, 0X0102, 0x2A19</td>
</tr>
<tr>
<td>0x0102</td>
<td>0x2A19(Battery Level)</td>
<td>0x04</td>
</tr>
<tr>
<td>0x0200</td>
<td>0x2800(Primary Service)</td>
<td>128UUIDBase</td>
</tr>
<tr>
<td>0x0201</td>
<td>0x2803(Characteristic)</td>
<td>0x12, 0X0202, 128UUID1</td>
</tr>
<tr>
<td>0x0202</td>
<td>128UUID1(temperature Characteristic)</td>
<td>0x028A(650*10^-2 = 6.5℃)</td>
</tr>
<tr>
<td>0x0203</td>
<td>0x2904(Characteristic Format)</td>
<td>0x0E（S16bit）, 0xFE(-2), 0x272F(Celsius), 0x01(Bluetooth SIG Assigned Numbers), 0x010C(Outside)</td>
</tr>
<tr>
<td>0x0204</td>
<td>0x2901(Characteristic User Description)</td>
<td>“Outside Temperature”</td>
</tr>
<tr>
<td>0x0205</td>
<td>0x2902(Client Characteristic Configuration Description)</td>
<td>0x0000</td>
</tr>
<tr>
<td>0x0210</td>
<td>0x2803(Characteristic)</td>
<td>0x12, 0X0211, 128UUID2</td>
</tr>
<tr>
<td>0x0211</td>
<td>128UUID2(Humidity Characteristic)</td>
<td>0x27</td>
</tr>
<tr>
<td>0x0212</td>
<td>0x2904(Characteristic Format)</td>
<td>0x04(unsigned 8-bit integer), 0x00, 0x27AD(percent), 0x01, 0x010C(Outside)</td>
</tr>
<tr>
<td>0x0213</td>
<td>0x2901(Characteristic User Description)</td>
<td>“Outside Relative Humidity”</td>
</tr>
<tr>
<td>0x0214</td>
<td>0x2902(Client Characteristic Configuration Description)</td>
<td>0x0000</td>
</tr>
</tbody></table>
<p>这个属性数据库提供了 4 个服务，分别是:</p>
<ul>
<li>GAP<ul>
<li>Device Name</li>
</ul>
</li>
<li>GATT<ul>
<li>无</li>
</ul>
</li>
<li>电池服务<ul>
<li>电量</li>
</ul>
</li>
<li>温湿度服务<ul>
<li>温度</li>
<li>湿度</li>
</ul>
</li>
</ul>
<blockquote>
<p>Handle 从 0x01到 0xFFFF，不能重复，允许不连续</p>
<p><a target="_blank" rel="noopener" href="https://www.bluetooth.com/specifications/gatt/services/">GATT Service UUID 定义</a></p>
<p><a target="_blank" rel="noopener" href="https://www.bluetooth.com/specifications/gatt/characteristics/">GATT Characteristic UUID 定义</a></p>
</blockquote>
<h2 id="发现流程"><a href="#发现流程" class="headerlink" title="发现流程"></a><strong>发现流程</strong></h2><p>使用BLE调试软件nRF Connect连接一个BLE设备，会随即列出BLE设备的服务项、特征值和描述符的信息。BLE主机获取从机的服务项等信息的过程，称为“发现（Discovery）”。<br>发现流程分三个步骤：</p>
<ul>
<li>发现全部服务项</li>
<li>发现全部特征值</li>
<li>发现全部描述符</li>
</ul>
<h3 id="发现服务项"><a href="#发现服务项" class="headerlink" title="发现服务项"></a>发现服务项</h3><p>发现服务有三种途径：发现所有的首要服务、按照服务项UUID发现指定的首要服务，查找包含服务。</p>
<p><strong>第一种途径</strong>，BLE主机向从机发送一个“按组类型读取请求（Read By Group Type Request）”，从机收到后，返回一个响应，包含了一个服务项的起始句柄和终止句柄。服务项中包含多个特征值和描述符，这些属性排列在GATT DB中，句柄值依次增加，所以起始句柄其实就是服务项自身的句柄，而终止句柄为该服务项下面的最后一个属性的句柄，越过该句柄，就是下一个服务项的领域。然后主机端再发送这样一个请求，从机再返回一个响应，等全部服务项都响应完毕，则返回错误。这样主机端就能够获取从机端的全部首要服务。</p>
<p><strong>第二种途径</strong>，BLE主机发送一个“按类型值查找请求（Find By Type Value Request）”，其实就是将服务项的UUID传给从机端，从机收到后，返回该UUID对应的服务项的起始句柄和终止句柄。这对于已知服务项UUID的情况，使用这种方法处理起来显然比第一种要快。</p>
<p><strong>第三种途径</strong>使用较少，这里不进行介绍。</p>
<h3 id="发现特征值"><a href="#发现特征值" class="headerlink" title="发现特征值"></a><strong>发现特征值</strong></h3><p>发现特征值一定是发现某个服务项领域内的特征值。与前面类似，BLE主机端向从机发送一个“按类型读取请求（Read By Type Request）”，从机端返回一个响应，包含了特征值信息。在实际操作中，开发这还会根据服务项的起始和终止句柄框定特征值的查找范围，这样多次执行，就可以获得该服务项下的全部特征值的句柄信息。</p>
<h3 id="发现描述符"><a href="#发现描述符" class="headerlink" title="发现描述符"></a>发现描述符</h3><p>BLE主机端向从机发送一个“查找信息请求（Find Info Request）”，从机端返回一个响应，包含了描述符信息。根据服务器的首尾句柄和特征值的句柄，可以推算出特征值的句柄范围，再通过查找信息请求，获得全部的描述符信息。显然，在固件程序中，获取全部以上信息，需要多轮循环操作，经历多次主从握手通信，即费时又易出错。Anroid开发的SDK中，将发现流程精简为一个API，一旦调用，则能获得全部的从机属性信息。有了从机端的属性结构信息，主机就可以向指定的句柄发送信息了，也可以知道从机发送过来的消息是哪个句柄触发的。</p>
<h2 id="客户端发起通信"><a href="#客户端发起通信" class="headerlink" title="客户端发起通信"></a>客户端发起通信</h2><p>有四种类型操作：</p>
<ul>
<li>读取特征值</li>
<li>写入特征值</li>
<li>读取描述符</li>
<li>写入描述符</li>
</ul>
<h3 id="读取特征值（Read）"><a href="#读取特征值（Read）" class="headerlink" title="读取特征值（Read）"></a>读取特征值（Read）</h3><p>读取特征值就是从机将GATT DB中的特征值数据读出来并发送给主机，所以这个操作可以分解成两部分，先从GATT DB将数据读出，再发送给主机。读取特征值数据要考虑特征值长度问题。默认的情况下MTU=23，这时读取的特征值数据长度不要超过22，否则会涉及到读取长数据。（Write属性值长度不应超过MTU-3，而Read属性值长度不应超过MTU-1，参考这个<a target="_blank" rel="noopener" href="https://community.cypress.com/docs/DOC-10704">文章</a>）</p>
<h3 id="使用UUID读取特征值"><a href="#使用UUID读取特征值" class="headerlink" title="使用UUID读取特征值"></a>使用UUID读取特征值</h3><p>BLE主机如果知道特征值的UUID，可以在不知道从机的属性结构的情况下，直接向从机发送读取指定UUID的特征值的请求，这样省却了发现的流程，快捷许多。</p>
<h3 id="写入特征值（Write）"><a href="#写入特征值（Write）" class="headerlink" title="写入特征值（Write）"></a>写入特征值（Write）</h3><p>与读取特征值类似。</p>
<h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>可靠写入（Reliable Write）和签名写入（Signed Write）看介绍很厉害，但是我从未使用过，这里不做多余介绍。</p>
<h2 id="服务端发起通信"><a href="#服务端发起通信" class="headerlink" title="服务端发起通信"></a>服务端发起通信</h2><p>只有两类：</p>
<ul>
<li>Notification</li>
<li>Indication</li>
</ul>
<p><strong>Notification</strong>可以在任何时候从BLE从机发给主机，主机端如果有足够的缓存空间，则一直接收，否则则丢弃。<strong>Indication</strong>则在发消息后，需要等待主机给出一个确认消息，在确认之前，不能再次发出，这样保证了通信的可靠。可以看出，这些内容与ATT层的属性协议是完全对应的。</p>

      </section>

    </article>
    
    


    <!-- 版权声明 -->
    <div class="post-copyright">
	<div>
		<strong>本文作者：</strong>
		<span>
			朝雨绫萧
		</span>
	</div>
	<div>
		<strong>本文链接：</strong>
		<a href="http://zzylxx.gitee.io/BLE GATT">
			http://zzylxx.gitee.io/BLE GATT
		</a>
	</div>
	<div>
		<span>欢迎任何形式的转载，但请务必注明出处。</span>
	</div>
	<div>
		<span>由于笔者水平有限，如果文章或代码有表述不当之处，还请不吝赐教。</span>
	</div>
</div>


<style>
	.post-copyright {
		background-color: #fff;
		/* box-shadow: 0px 0px 1px 1px rgba(0, 0, 0, 0.05); */
		box-shadow: 0 6px 30px -10px rgba(0, 0, 0, 0.1);
		display: block;
		margin: 20px auto;
		margin-bottom: 15px;
		padding: 10px;
		font-size: 15px;
		color: #2e2d2d;
		word-break: break-all;
		line-height: 1.8;
	}


	.post-copyright a {
		color: #2e2d2d;
		border-bottom: 1px solid #999;
	}

	.post-copyright a:hover {
		color: #2e2d2d;
		border-bottom: 1px solid #000;
	}
</style>

    <!-- comments -->
    <div class="comments">
    <h3 id="commenttitle" style="margin-top: 20px;margin-left: 10px;">评论区</h3>
    <div id="tcomment"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/twikoo@1.0.0/dist/twikoo.all.min.js"></script>
<script>twikoo.init({ envId: 'twikoo-1gx61ki809c44dc7', el: '#tcomment' })</script>

    <div class="right-icon">
      <div id="to-valine" class="right-icon-item">
        <a href="#commenttitle">评论</a>
      </div>
    </div>

  </div>


  <aside>
    <div class="aside-wrap">
    
        <section class="widget-wrap">
    <h3 class="widget-title">简介</h3>
    <div class="widget">
        <div class="notice">
            <img class="notice-img" src="/image/head_portrait.jpg">
            <p>路漫漫其修远兮，<br>吾将上下而求索!<br></p>
            
            <p class="notice-item">
                <!-- github -->
                <span><a target="_blank" href="https://github.com/qgyhd1234"><img src="https://shuxhan-imgbed.oss-cn-hangzhou.aliyuncs.com/img/20210108165043.jpg" alt="Github"></a></span>
                <!-- juejin -->
                <!-- <span><a target="_blank" href=""><img src="https://shuxhan-imgbed.oss-cn-hangzhou.aliyuncs.com/img/20210108165800.png" alt="掘金社区"></a></span> -->
                <!-- zhihu -->
                <!-- <span><a target="_blank" href=""><img src="https://shuxhan-imgbed.oss-cn-hangzhou.aliyuncs.com/img/20210108172745.ico" alt="知乎"></a></span> -->
                <!-- csdn -->
                <!-- <span><a target="_blank" href=""><img src="https://shuxhan-imgbed.oss-cn-hangzhou.aliyuncs.com/img/20210108172932.ico" alt="CSDN"></a></span> -->
            </p>

            <ul class="statistics">
                <li>
                    <span>文章</span>
                    <a href="./archives">7</a>
                </li>
                <li>
                    <span>字数</span>
                    <a>9.7k</a>
                </li>
                <li>
                    <span>访客</span>
                    <a id="busuanzi_value_site_uv"></a>
                </li>
                
                
            </ul>

            <div id="follow" class="button">
                <a target="_blank" href="https://github.com/shuxhan">关注</a>
            </div>
        </div>
    </div>
</section>

    
        
  <section class="widget-wrap">
    <h3 class="widget-title">文章分类</h3>
    <div class="widget">
      <div class="categories-list">
        
           
            
              <a class="category-list-item" href="/categories/BLE">
                <span class="category-list-link">BLE</span>
                <span class="category-list-number">(7)</span>
              </a>
            
          
        
      </div>
    </div>
  </section>


    
        
  <div class="widget-wrap">
    <h3 class="widget-title">标签</h3>
    <div class="widget">
      <ul class="tag-list" itemprop="keywords"><li class="tag-list-item"><a class="tag-list-link" href="/tags/BLE/" rel="tag">BLE</a></li></ul>
    </div>
  </div>


    
        
  <div class="widget-wrap">
    <h3 class="widget-title">最新文章</h3>
    <div class="widget">
      <ul class="recent-posts">
        
          <li>
            <a href="/2021/02/28/GAP%E5%8F%82%E6%95%B0%E8%AE%BE%E7%BD%AE/">BLE GAP参数设置</a>
          </li>
        
          <li>
            <a href="/2021/02/28/BLE%E5%B9%BF%E6%92%AD%E5%92%8C%E6%89%AB%E6%8F%8F%E5%93%8D%E5%BA%94/">BLE 广播和扫描响应</a>
          </li>
        
          <li>
            <a href="/2021/02/28/BLE%20%E8%BF%9E%E6%8E%A5/">BLE 连接</a>
          </li>
        
          <li>
            <a href="/2021/02/28/BLE%20SM/">BLE Security Manager</a>
          </li>
        
          <li>
            <a href="/2021/02/28/BLE%20GATT/">BLE GATT</a>
          </li>
        
      </ul>
    </div>
  </div>

    
        
  <section class="widget-wrap">
    <h3 class="widget-title">归档</h3>
    <div class="widget">
      <ul class="archive-list"><li class="archive-list-item"><a class="archive-list-link" href="/archives/2021/02/">二月 2021</a></li></ul>
    </div>
  </section>


    
    
</div>


    <!-- 目录 -->
    
  </aside>
  
</div>

					</div>
				</div>

				<!-- 返回顶部按钮 -->
				<div class="right-icon">
					<div id="to-top" class="right-icon-item">
						<a>
							<span class="top-left"></span>
							<span class="top-right"></span>
						</a>
					</div>
				</div>
		</div>
		<footer class="footer">

    <div class="copyright-wrap">
      <span>&copy; 2021-2021 朝雨绫萧</span>
      <span id="timeDate">载入天数...</span><span id="busuanzi_container_site_pv">
      总访问量<span id="busuanzi_value_site_pv"></span>次
    </span>
    </div>
    
    
    <!-- <span id="busuanzi_container_site_uv">
      ，本站访客数<span id="busuanzi_value_site_uv"></span>人次
    </span> -->
    
</footer>

<script>
    var now = new Date();
    function createtime() {
      var grt = new Date("2/27/2021 16:12:00");//此处修改你的建站时间或者网站上线时间 
      now.setTime(now.getTime() + 250);
      days = (now - grt) / 1000 / 60 / 60 / 24; dnum = Math.floor(days);
      document.getElementById("timeDate").innerHTML = "本站已安全运行 " + dnum + " 天 ";
    }
    setInterval("createtime()", 1000);
</script>

<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

<script src="/lib/jquery.js"></script>


<script src="/lib/waves.js"></script>


<script src="/lib/jquery-ui.js"></script>


<script src="/lib/jquery.tocify.js"></script>


<script src="/js/main.js"></script>


			
<style>
  .copy-btn {
    display: inline-block;
    padding: 6px 12px;
    font-size: 13px;
    font-weight: 500;
    line-height: 20px;
    color: rgb(206, 206, 206);
    white-space: nowrap;
    vertical-align: middle;
    cursor: pointer;
    border-radius: 3px;
    user-select: none;
    outline: 0;
  }

  .copy-btn:hover {
    color: #888
  }

  .highlight-wrap .copy-btn {
    padding: 2px 6px;
    position: absolute;
    right: 10px;
    top: 1px;
    z-index: 1;
  }



  .highlight-wrap {
    position: relative;
  }

  #copysuccess {
    width: 200px;
    height: 75px;
    position: fixed;
    right: 50%;
    margin-right: -100px;
    top: 40%;
    margin-top: -40px;
    color: #fff;
    font-weight: 600;
    background: #444444d8;
    display: none;
    z-index: 99999;
    border-radius: 10px;
    text-align: center;
    line-height: 30px;
    padding-top: 10px;
    user-select: none;
  }
</style>

<script>
  addLoadEvent(() => {
    $('.highlight').each(function (i, e) {
      var $wrap = $('<div>').addClass('highlight-wrap')
      $(e).after($wrap)
      $wrap.append($('<div>').addClass('copy-btn').append('复制代码').on('click', function (e) {
        var copysuccess = document.getElementById('copysuccess');
        copysuccess.style.display = 'block'
        setTimeout(function () {
          copysuccess.style.display = 'none'
        }, 2000)
        var code = $(this).parent().find(".code")[0].innerText
          
                var ta = document.createElement('textarea')
        document.body.appendChild(ta)
        ta.style.position = 'absolute'
        ta.style.top = '0px'
        ta.style.left = '0px'
        ta.value = code
        ta.select()
        ta.focus()
        var result = document.execCommand('copy')
        document.body.removeChild(ta)
          
                  if (result) $(this).text('复制成功')
            else $(this).text('复制失败')
              
                $(this).blur()
      })).on('mouseleave', function (e) {
        var $b = $(this).find('.copy-btn')
        setTimeout(function () {
          $b.text('复制代码')
        }, 1000)
      }).append(e)
    })
  })
</script>

				<div id="copysuccess">复制成功<br>欢迎阅读文章~</div>
	</body>

</html>

<script>
	$('#to-top').click(function () { $('html,body').animate({ scrollTop: '0px' }, 500); return false; });
</script>